
#ifndef __NETWORKFILE_H__
#define __NETWORKFILE_H__

#include "Common/NetworkSocket.h"
#include "Common/NetworkProcessor.h"
#include "CachedFileManager.h"

namespace SPlayer
{
    namespace Network
    {
        using namespace System;
        using namespace System::Net;
        using namespace System::Net::Sockets;
        using namespace System::Collections;
        using namespace System::Collections::Generic;
        using namespace SPlayer;
        using namespace System::IO;
        ref class NetworkMusicFile;
        ref class NetwrokFileTraceHelper;

        enum class NetworkMusicFileState
        {
            Ready, 
            Sent,
            Received,
            Error
        };
        ref class NetworkMusicFileEventDispatcher
        {
        public:
            Action ^onDownloadFinished;
            Action ^onError;
        };
        ref class NetworkMusicFile
        {
        public:
            /*! @note Should not gcnew a NetworkFile by yourself, \
             *! use NetworkFileManager::getFile
             *!
             *! @sa NetworkFileManager::getFile*/
            NetworkMusicFile(String ^musicTitle) : musicTitle(musicTitle)
            {
                if (CachedFileManager::isMusicFileCached(MusicPathInfo::toNetworkPath(musicTitle)))
                {
                    FileStream f(CachedFileManager::toMusicCachedPath(musicTitle), FileMode::Open, FileAccess::Read, FileShare::ReadWrite);

                    this->fileSize = static_cast<int>(f.Length);
                    this->filedata = gcnew array<unsigned char>(fileSize);
                    this->currentReceivePosition = fileSize;
                    f.Read(filedata, 0, fileSize);
                    this->state = NetworkMusicFileState::Received;
                    f.Close();
                }
            }

            /*! @brief Try to make sure data at \a offset available
             *! If data at \a offset is not available yet,
             *! NetworkFile will drop original socket and create
             *! a new one to request SPlayer Server to send required
             *! data to here.
             *! */
            void startDownload()
            {
                if (state == NetworkMusicFileState::Sent || state == NetworkMusicFileState::Received)
                {
                    // downloading or downloaded
                    return;
                }
                state = NetworkMusicFileState::Ready;

                socket = gcnew NetworkSocket;
                socket->connectAsync();
                socket->eventDispatcher->onConnectionFinished =
                    gcnew Action(this, &NetworkMusicFile::onConnected);
            }
            void abort()
            {
                if (state == NetworkMusicFileState::Sent)
                {
                    state = NetworkMusicFileState::Error;
                    // abort, don't call error
                    //Function::InvokeSync(eventDispatcher->onError);
                }
                if (socket)
                {
                    socket->Close();
                }
                if (processor)
                {
                    processor->close();
                }
            }

        public:
            int read(array<unsigned char> ^buffer, int offset, int count)
            {
                count = Math::Min(count, fileSize - readPosition);
                Array::Copy(filedata, readPosition, buffer, offset, count);

                readPosition += count;
                return count;
            }
            int seek(int offset, System::IO::SeekOrigin origin)
            {
                switch (origin)
                {
                default:
                case System::IO::SeekOrigin::Begin:
                readPosition = offset;
                break;
                case System::IO::SeekOrigin::Current:
                readPosition += offset;
                break;
                case System::IO::SeekOrigin::End:
                fileSize = fileSize - offset;
                break;
                }
                return readPosition;
            }
            bool isReadable(int offset, int size)
            {
                size = Math::Max(1, size);
                if (nullptr == goodBits)
                    return false;
                if (state == NetworkMusicFileState::Received)
                    return true;
                if (offset > receiveStartPosition && offset + size < currentReceivePosition)
                    return true;
                for (int i = offset; i < offset + size; i++)
                {
                    if (goodBits[i] == false)
                        return false;
                }
                return true;
            }
        protected:
            void onConnected()
            {
                if (socket->Connected)
                {
                    processor = gcnew NetworkProcessor(socket);
                    processor->eventDispatcher->onParamReceived +=
                        gcnew Action(this, &NetworkMusicFile::onParamReceived);
                    processor->eventDispatcher->onDataInProgress +=
                        gcnew Action(this, &NetworkMusicFile::onDataInProgress);
                    processor->eventDispatcher->onDataReceiveFinished +=
                        gcnew Action(this, &NetworkMusicFile::onDataAllReceived);
                    processor->eventDispatcher->onError +=
                        gcnew Action<Exception ^>(this, &NetworkMusicFile::onError);
                    processor->eventDispatcher->onClosed +=
                        gcnew Action(this, &NetworkMusicFile::onClosed);

                    auto params = processor->sendParams;
                    params->add("Request", "DownloadMusic");
                    params->add("Title", musicTitle);
                    params->add("Offset", 0);
                    params->add("Size", 0);
                    processor->sendAsync();
                    state = NetworkMusicFileState::Sent;
                }
                else if (state != NetworkMusicFileState::Error)
                {
                    state = NetworkMusicFileState::Error;
                    Function::InvokeSync(eventDispatcher->onError);
                }
            }
            void onParamReceived()
            {
                if (fileSize == 0)
                {
                    fileSize = processor->receivedParams["FileSize"];

                    receiveStartPosition = processor->receivedParams["Offset"];
                    filedata = gcnew array<unsigned char>(fileSize);
                    goodBits = gcnew BitArray(fileSize, false);
                }
            }
            System::DateTime ^enterTime = nullptr;
            void onDataInProgress()
            {
                const int downloadRate = 1024 * 100 / 1000;

                if (enterTime == nullptr)
                {
                    enterTime = gcnew System::DateTime;
                    *enterTime = System::DateTime::Now;
                }
                else
                {
                    while (((System::DateTime::Now - *enterTime).TotalMilliseconds * downloadRate) < currentReceivePosition)
                    {
                        Threading::Thread::Sleep(0);
                    }
                }

                int newDataLength = processor->receivedDataSize - (currentReceivePosition - receiveStartPosition);
                Array::Copy(processor->receivedData, currentReceivePosition - receiveStartPosition,
                            this->filedata, currentReceivePosition, newDataLength);
                for (int i = currentReceivePosition; i < currentReceivePosition + newDataLength; i++)
                {
                    goodBits[i] = true;
                }
                currentReceivePosition += newDataLength;
            }
            void onDataAllReceived()
            {
                checkData();
                processor->close();
            }
            void onClosed()
            {
                checkData();
                //socket = nullptr;
                //processor = nullptr;
            }
            void onError(System::Exception ^e)
            {
                processor->close();
            }
        protected:
            void checkData()
            {
                if (state == NetworkMusicFileState::Received)
                    goto _ok;
                else if (state == NetworkMusicFileState::Error)
                    goto _err;
                else if (state == NetworkMusicFileState::Ready)
                    return;
                if (receiveStartPosition == 0 && currentReceivePosition == fileSize)
                    goto _ok;
                if (currentReceivePosition == fileSize)
                {
                    for (int i = 0; i < receiveStartPosition; i++)
                    {
                        if (goodBits[i] == false)
                            goto _err;
                    }
                    goto _ok;
                }

                for (int i = 0; i < fileSize ; i++)
                {
                    if (goodBits[i] == false)
                        goto _err;
                }
_err:
                if (state != NetworkMusicFileState::Error)
                {
                    state = NetworkMusicFileState::Error;
                    Function::InvokeSync(eventDispatcher->onError);
                }
                return;
_ok:
                state = NetworkMusicFileState::Received;
                Function::InvokeSync(eventDispatcher->onDownloadFinished);
            }
        public:
            NetworkMusicFileEventDispatcher ^eventDispatcher = gcnew NetworkMusicFileEventDispatcher;
        public:
            String ^musicTitle;
            array<unsigned char> ^filedata;
            int fileSize = 0;
            int readPosition = 0;
            int currentReceivePosition = 0;
            NetworkMusicFileState state = NetworkMusicFileState::Ready;

        private:
            BitArray ^goodBits;
            int receiveStartPosition = 0;
            NetworkSocket ^socket = nullptr;
            NetworkProcessor ^processor = nullptr;
        };
    }
}

#endif // __NETWORKFILE_H__